package server;

import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Vector;

import config.ConfigurationException;
import config.IRecursoHandler;
import config.Log;
import config.RecursoXmlHandler;
import core.IMessageReader;
import core.IMessageWriter;
import core.IServer;
import core.MessageType;
import core.Receive;
import core.Recurso;
import core.Sender;
import core.Util;

public class Middleware implements IMessageReader, IMessageWriter, IServer {
	
	// A chave é o nome do recuros e o valor é o objeto do recurso
	private Hashtable<String, Recurso> recursosServer = new Hashtable<String, Recurso>();
	// A chave é o nome do recurso e o valor é a fila de clientes
	private Hashtable<String, Queue<ClienteTempo>> filaClientes = 
		new Hashtable<String, Queue<ClienteTempo>>();
	//Fila de execuções de thread
	private Hashtable<String, Thread> listaExecucao = new Hashtable<String, Thread>();
	
	private String endereco;
	private String canal;
	private Receive receive;
	private Sender sender;
	private IRecursoHandler recursoHandler;
	
	public Middleware(String endereco, String canal) {
		this.endereco = endereco;
		this.canal = canal;
		
		this.receive = new Receive(this);
		this.sender = new Sender();
		
		try {
			this.recursoHandler = new RecursoXmlHandler(this.canal);
		
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}
		
		messageSystemInit(endereco, canal);
		recursosInit();
	}

	private void messageSystemInit(String endereco, String nome){
		try {
			Log.getInstance(Middleware.class).getLogger().info(
					"Iniciando leitura e escrita em JMS - " + Util.getTime()
			);
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}
		
		receive.inicia(endereco, nome);
		sender.inicia(endereco, nome);
	}
	
	private void recursosInit(){
		try{
			Vector<Recurso> recursos = this.recursoHandler.listRecursos();
			for (Recurso recurso : recursos){
				this.recursosServer.put(recurso.getNome(), recurso);
			}
			
		}catch (ConfigurationException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void readMessage(String message) {
		try {
			Log.getInstance(Middleware.class).getLogger().info(
					"Lendo mensagem em: " + this.canal + "; " +
							"mensagem: " + message + " - " + Util.getTime()	
			);
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}
		
		String[] messages = message.split("\\" + MessageType.SPLIT);
		if (messages.length > 1){
			if (MessageType.LISTAGEM_RECURSOS.toString().equals(messages[0])){
				String result = MessageType.LISTAGEM_RECURSOS.toString() + 
				MessageType.SPLIT + this.canal + MessageType.SPLIT;
				for (Map.Entry<String, Recurso> entry : recursosServer.entrySet()){
					result += entry.getKey() + MessageType.SPLIT;
				}
				
				String clientNome = messages[1];
				writeMessage(this.endereco, clientNome, result);
			
			// Adição de recurso feita por ADRecurso
			} else if (MessageType.ADICIONAR_RECURSO.toString().equals(messages[0])){
				Recurso recurso = new Recurso();
				recurso.setNome(messages[1]);
				recurso.setDescricao(messages[2]);
				recurso.setMaquina(messages[3]);
				recurso.setLocal(messages[4]);
				try {
					recursoHandler.addRecurso(recurso);
					recursosServer.put(recurso.getNome(), recurso);
				} catch (ConfigurationException e) {
					e.printStackTrace();
				}
			}
			
			else if (MessageType.SOLICITACAO_RECURSO.toString().equals(messages[0])){
				// Verifica se o servidor possui o recurso que o cliente pediu
				if (recursosServer.containsKey(messages[2])) {
					inserirFilaEspera(messages[1], messages[2], messages[3]);
					verificaExecucoes(messages[1], messages[2]);
				}
			}
			
			// Ocliente recusa o cliente por que já usou ou está usando de outro servidor
			else if (MessageType.RECUSAR_RECURSO.toString().equals(messages[0])) {
				// Verifica se o servidor possui o recurso que o cliente recusou
				if (recursosServer.containsKey(messages[2])) { 
					
					// Se estiver executando, mata a execução e retira ele da fila
					if (listaExecucao.containsKey(messages[1]+"|"+messages[2])) {
						finalizaExecucaoRecurso(messages[2], messages[1], true);
					// Senão simplesmente tira ele ta fila de espera
					} else {
						removerFilaEspera(messages[2], messages[1]);
					}
				}
			}
		}
	}
	
	/**
	 * Se foi adicionado um novo cliente, então verifica se a fica para o 
	 * determinado recurso, a fila está vazia. Se estiver executa.
	 * 
	 * Se este método foi chamado do fim da execução da thread, então é
	 * verificado se existe mais alguém esperando. Se estiver executa.
	 */
	public void verificaExecucoes(String proxCliente, String recurso) {
		
		// Uma verificação se não for executado imediatamente, para informar ]
		// o cliente
		if ((proxCliente != null) && (filaClientes.get(recurso).size() > 1)) {
			
			String msg = MessageType.ESPERA_RECURSO.toString() + MessageType.SPLIT 
			+ this.canal + MessageType.SPLIT + recurso;
			writeMessage(this.endereco, proxCliente, msg);
		}
	
		else if (((proxCliente != null) && (filaClientes.get(recurso).size() == 1)) || 
			((proxCliente == null) && !filaClientes.get(recurso).isEmpty())) {
		
			UtilizaRecurso thread = new UtilizaRecurso(recurso);
			String clienteKey = filaClientes.get(recurso).peek().getCliente();
			listaExecucao.put(clienteKey+"|"+recurso, thread);
			
			// Notifica o cliente de que o recurso está pronto e já está 
			// sendo utilizado
			String msg = MessageType.LIBERACAO_RECURSO.toString() + MessageType.SPLIT 
			+ this.canal + MessageType.SPLIT + recurso;
			
			writeMessage(this.endereco, clienteKey, msg);
			thread.start();
		}
	}

	public void finalizaExecucaoRecurso(String recurso, String cliente, 
			boolean interrupt) {
		
		// Cliente abortando determinada execução
		if (interrupt) {
			listaExecucao.get(cliente+"|"+recurso).interrupt();
		}
		
		// Remove o cliente que já utilizou o recurso
		removerFilaEspera(recurso, cliente);
		
		// Finaliza a notificação de processo em execução
		listaExecucao.remove(cliente+"|"+recurso);
		
		//tipo da mensagem: finaliza_uso_do_recurso|server1|t2
		// Notifica o cliente de que o recurso terminou sua execução
		String msg = MessageType.FINALIZA_RECURSO.toString() + MessageType.SPLIT 
			+ this.canal + MessageType.SPLIT + recurso;
		if (interrupt) {
			msg += " ---> Recurso interrompido";
		}
		writeMessage(this.endereco, cliente, msg);
	}
	
	@Override
	public void writeMessage(String endereco, String nome, String message) {
		try {
			Log.getInstance(Middleware.class).getLogger().info(
					"Escrevendo mensagem de: " + this.canal + " para " + nome + "; " +
							"mensagem: " + message + " - " + Util.getTime()	
			);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		Sender sender = new Sender();
		sender.inicia(endereco, nome);
		sender.sendMessage(message);
	}
	
	@Override
	public void inserirFilaEspera(String cliente, String recurso, String tempo) {
		try {
			Log.getInstance(Middleware.class).getLogger().info(
					"Inserindo canal " + cliente +
					" na lista de espera em " + this.canal +
					" para utilização do recurso " + recurso + " por tempo " + tempo + "s" +
					" - " + Util.getTime()
			);
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}
		
		/**
		 * Verifica se já possui uma fila para o determinado recurso
		 * O cliente é adicionado na fila do recurso concatenado com
		 * o tempo que ele irá usar o recurso
		 */ 
		ClienteTempo clienteTempo = new ClienteTempo(cliente, tempo);
		
		if (filaClientes.containsKey(recurso)) {
			filaClientes.get(recurso).add(clienteTempo);
		} else {
			Queue<ClienteTempo> espera = new LinkedList<ClienteTempo>();
			espera.add(clienteTempo);
			filaClientes.put(recurso, espera);
		}
	}

	@Override
	public void removerFilaEspera(String recurso, String cliente) {
		try {
			Log.getInstance(Middleware.class).getLogger().info(
					"Removendo canal " + cliente +
					" da lista de espera em " + this.canal +
					" para utilização do recurso " + recurso +
					" - " + Util.getTime()
			);
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}
		
		this.filaClientes.get(recurso).remove(new ClienteTempo(cliente));
	}
	
	private class UtilizaRecurso extends Thread{
		private String recursoNome;
		private String clienteNome;
		private long tempoUtilizacao = 1l;
		
	
		public UtilizaRecurso(String recursoNome) {
			this.recursoNome = recursoNome;
			this.clienteNome = filaClientes.get(recursoNome).peek().getCliente(); 
			this.tempoUtilizacao = 1000 * Long.parseLong(filaClientes.get(recursoNome).peek().getTempo());
		}
		
		@Override
		public void run() {
			try {
				Log.getInstance(Middleware.class).getLogger().info(
						"Executando thread para utilização de recurso em: " + canal + "; " +
						"cliente: " + this.clienteNome + "; "  +
						"recurso: " + this.recursoNome + "; " +
						"tempo: " + this.tempoUtilizacao + "ms"  +
						" - " + Util.getTime()
				);
				
				sleep(this.tempoUtilizacao);
				
				// Filaliza dados de execução do recurso passivamente
				finalizaExecucaoRecurso(this.recursoNome, this.clienteNome, false);
				
				// Verifica se existe mais alguém esperando pelo recurso
				verificaExecucoes(null, this.recursoNome);
				
				
			} catch (InterruptedException e) {
			} catch (ConfigurationException e) {
				e.printStackTrace();
			}
		}
	}
	
	private class ClienteTempo {
		private String cliente;
		private String tempo;
		
		//Construtor para verificar se cliente esta na fila de clientes (dos recursos) 
		public ClienteTempo(String cliente) {
			this.cliente = cliente;
		}
		
		public ClienteTempo(String cliente, String tempo) {
			this.cliente = cliente;
			this.tempo = tempo;
		}

		public String getCliente() {
			return cliente;
		}

		public void setCliente(String cliente) {
			this.cliente = cliente;
		}

		public String getTempo() {
			return tempo;
		}

		public void setTempo(String tempo) {
			this.tempo = tempo;
		}

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof ClienteTempo)){
				return false;
			}else{
				ClienteTempo clienteTempo = (ClienteTempo) obj;
				return this.cliente.equals(clienteTempo.getCliente());
			}
		}
	}
}
